package com.liuyk.commonlib.network;

import android.text.TextUtils;
import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.liuyk.commonlib.network.request.BaseFileRequest;
import com.liuyk.commonlib.network.request.BaseRequest;
import com.liuyk.commonlib.network.request.BaseTypeRequest;
import com.liuyk.commonlib.network.response.BaseArrayResponse;
import com.liuyk.commonlib.network.response.BaseResponse;
import com.liuyk.commonlib.utility.JsonUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;

/**
 * 真正发起请求
 * <p>
 * Created by liuyakui on 2020/1/17.
 */
public class RealCall {
    private static final String TAG = "OkHttp";

    private final ConcurrentHashMap<String, Subscription> mSubMap;

    private RealCall() {
        mSubMap = new ConcurrentHashMap<>();
    }

    public static RealCall getInstance() {
        return Inner.INSTANCE;
    }

    public <T> void put(BaseRequest request, NetworkCallBack<T> callBack) {
        BaseTypeRequest typeRequest;
        Map params;
        if (request instanceof BaseTypeRequest) {
            typeRequest = (BaseTypeRequest) request;
            params = typeRequest.getTypeParams();
        } else {
            params = request.getParams();
        }

        Subscription mSubscription = executor(request,
                ApiInit.getInstance().getApiInterface().put(
                        request.getUrl(), getRequestBody(request.getUrl(), params, false),
                        request.getHeader()),
                callBack);

        mSubMap.put(request.getUrl(), mSubscription);
    }

    public <T> void delete(BaseRequest request, NetworkCallBack<T> callBack) {
        BaseTypeRequest typeRequest;
        Map params;
        if (request instanceof BaseTypeRequest) {
            typeRequest = (BaseTypeRequest) request;
            params = typeRequest.getTypeParams();
        } else {
            params = request.getParams();
        }

        Subscription mSubscription = executor(request,
                ApiInit.getInstance().getApiInterface().delete(
                        request.getUrl(),
                        request.getHeader()),
                callBack);

        mSubMap.put(request.getUrl(), mSubscription);
    }

    public <T> void get(BaseRequest request, final NetworkCallBack<T> callBack) {

        Subscription mSubscription = executor(request,
                ApiInit.getInstance().getApiInterface().get(
                        request.getUrl(),
                        request.getParams(),
                        request.getHeader()),
                callBack);

        mSubMap.put(request.getUrl(), mSubscription);
    }

    public <T> void post(BaseRequest request, NetworkCallBack<T> callBack) {
        BaseTypeRequest typeRequest;
        Map params;
        if (request instanceof BaseTypeRequest) {
            typeRequest = (BaseTypeRequest) request;
            params = typeRequest.getTypeParams();
        } else {
            params = request.getParams();
        }

        Subscription mSubscription = executor(request,
                ApiInit.getInstance().getApiInterface().post(
                        request.getUrl(), getRequestBody(request.getUrl(), params, false),
                        request.getHeader()),
                callBack);

        mSubMap.put(request.getUrl(), mSubscription);
    }

    //上传文件
    public <T> void postFile(BaseFileRequest request, NetworkCallBack<T> callBack) {
        if (request.getSingleFile() != null) {
            Subscription mSubscription = executor(request,
                    ApiInit.getInstance().getApiInterface().postFile(request.getUrl(),
                            getFileRequestBody(request.getSingleFile(), request.getParams(), "file"),
                            request.getHeader()),
                    callBack);

            mSubMap.put(request.getUrl(), mSubscription);
        }
        if (request.getMultipartFile() != null && request.getMultipartFile().length > 0) {
            Subscription mSubscription = executor(request,
                    ApiInit.getInstance().getApiInterface().postFiles(request.getUrl(),
                            getFilesRequestBody(request.getMultipartFile(), request.getParams(), "file"),
                            request.getHeader()),
                    callBack);

            mSubMap.put(request.getUrl(), mSubscription);
        }
    }

    private <T> Subscription executor(final BaseRequest request, Observable<ResponseBody> observable, final NetworkCallBack<T> callBack) {

        return observable.subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<ResponseBody>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                        mSubMap.remove(request.getUrl());
                        printNetworkLog(request, e.toString());
                        if (callBack != null) {
                            callBack.onError(request, new ServerException(e));
                        }
                    }

                    @Override
                    public void onNext(ResponseBody responseBody) {
                        mSubMap.remove(request.getUrl());
                        final Gson g = new Gson();
                        String json = "";
                        T response;
                        try {
                            json = responseBody.string();
                            response = g.fromJson(json, getInterfaceType(callBack));

                            if (response != null) {
                                if (response instanceof BaseResponse) {
                                    BaseResponse baseResponse = (BaseResponse) response;
                                    baseResponse.respJson = json;

                                    if (callBack != null) {
                                        callBack.onSuccess(request, (T) baseResponse);
                                    }

                                } else if (response instanceof BaseArrayResponse) {
                                    BaseArrayResponse arrayResponse = (BaseArrayResponse) response;
                                    ((BaseArrayResponse) response).respJson = json;

                                    if (callBack != null) {
                                        callBack.onSuccess(request, (T) arrayResponse);
                                    }
                                } else {
                                    if (callBack != null) {
                                        callBack.onSuccess(request, response);
                                    }
                                }
                            }
                        } catch (Exception e) {
                            printNetworkLog(request, e.toString());
                            e.printStackTrace();
                            if (callBack != null) {
                                callBack.onError(request, new ServerException(e));
                            }
                        } finally {
                            printNetworkLog(request, json);
                        }
                    }
                });
    }

    private void printNetworkLog(BaseRequest request, String msg) {
        try {
            List<String> strings = JsonFormatUtils.formatJson(msg);
            printLine(request, true);
            for (String line : strings) {
                Log.d(TAG, "║ " + line);
            }
            printLine(request, false);
        } catch (Exception ignored) {
        }
    }

    public static void printLine(BaseRequest request, boolean isTop) {
        try {
            if (isTop) {
                Log.w(TAG, "╔══════════════════════════════════════════════════════════════════════");
                Log.e(TAG, "url: " + request.getUrl());
                if (request instanceof BaseTypeRequest) {
                    Log.e(TAG, "params: " + JsonUtils.toJsonText(((BaseTypeRequest) request).getTypeParams()));
                } else {
                    Log.e(TAG, "params: " + JsonUtils.toJsonText(request.getParams().toString()));
                }
            } else {
                Log.w(TAG, "╚══════════════════════════════════════════════════════════════════════");
            }
        } catch (Exception ignored) {
        }
    }

    /**
     * download
     *
     * @param url        url
     * @param file       file
     * @param breakpoint breakpoint
     * @param callBack   callback
     */
    public void download(final String url, final File file, final boolean breakpoint, final DownloadCallBack callBack) {
        long progress = DownloadManager.getInstance().getProgress(url);
        Subscription mSubscription = ApiInit.getInstance().getApiInterface().download(url, "bytes=" + progress + "-")
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .map(new Func1<ResponseBody, ResponseBody>() {
                    @Override
                    public ResponseBody call(ResponseBody responseBody) {
                        return responseBody;
                    }
                })
                .observeOn(Schedulers.io())
                .doOnNext(new Action1<ResponseBody>() {
                    @Override
                    public void call(ResponseBody responseBody) {
                        if (breakpoint) {
                            downloadRandomAccessFile(url, file, responseBody, callBack);
                        } else {
                            download(url, file, responseBody, callBack);
                        }
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<ResponseBody>() {
                    @Override
                    public void onCompleted() {
                        mSubMap.remove(url);
                    }

                    @Override
                    public void onError(Throwable e) {
                        mSubMap.remove(url);
                        if (callBack != null) {
                            callBack.downloadFail(url, e.toString());
                        }
                    }

                    @Override
                    public void onNext(ResponseBody responseBody) {

                    }
                });

        mSubMap.put(url, mSubscription);
    }

    //支持断点下载
    private void downloadRandomAccessFile(String url, File file, ResponseBody responseBody, final DownloadCallBack callBack) {
        InputStream in = null;
        try {
            //创建RandomAccessFile
            RandomAccessFile rwdFile = DownloadManager.getInstance().createRWDFile(file);
            if (rwdFile == null) {
                callBack.downloadFail(url, "文件创建失败");
                return;
            }
            //查找进度,并且移动文件光标位置
            long progress = DownloadManager.getInstance().getProgress(url);
            rwdFile.seek(progress);

            //写入文件
            final byte[] buff = new byte[1024 * 64];
            final long l = responseBody.contentLength();
            final long originL = DownloadManager.getInstance().getLength(url);//源长度
            int len;
            in = responseBody.byteStream();
            while ((len = in.read(buff)) != -1) {
                rwdFile.write(buff, 0, len);
                progress = progress + len;
                if (callBack != null) {
                    long tempL = originL > 0 ? originL : l;
                    //百分比
                    int percent = (int) ((float) progress / (float) tempL * 100f);
                    callBack.downloading(url, tempL, progress, percent);
                }
                DownloadManager.getInstance().setProgress(url, l, progress);
            }
            rwdFile.close();
            if (callBack != null) {
                callBack.downloadSuccess(url, file);
            }
            DownloadManager.getInstance().removeProgress(url);
        } catch (Exception e) {
            e.printStackTrace();
            if (callBack != null) {
                callBack.downloadFail(url, e.getMessage());
            }
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void download(String url, File file, ResponseBody responseBody, final DownloadCallBack callBack) {
        if (responseBody == null) {
            callBack.downloadFail(url, "");
            return;
        }

        final byte[] buf = new byte[1024 * 64];
        FileOutputStream fos = null;
        InputStream in = null;
        try {
            final long l = responseBody.contentLength();
            in = responseBody.byteStream();
            fos = new FileOutputStream(file);
            int len;
            int progress = 0;
            while ((len = in.read(buf)) != -1) {
                fos.write(buf, 0, len);
                progress = progress + len;
                int percent = (int) ((float) progress / (float) l * 100f);
                if (callBack != null) {
                    callBack.downloading(url, l, progress, percent);
                }
            }
            fos.flush();
            if (callBack != null) {
                callBack.downloadSuccess(url, file);
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (callBack != null) {
                callBack.downloadFail(url, e.getMessage());
            }
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //获取接口泛型
    private Type getInterfaceType(Object o) {
        try {
            Type genericInterface = o.getClass().getGenericInterfaces()[0];
            ParameterizedType pt = (ParameterizedType) genericInterface;
            return pt.getActualTypeArguments()[0];
        } catch (Exception e) {
            return null;
        }
    }

    public RequestBody getRequestBody(String url, Map req, boolean isSign) {
        if (isSign) {
            //如果加密
        }
        String json = toJsonText(req);
        return RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
    }

    public MultipartBody.Part getFileRequestBody(File file, Map<String, String> params, String key) {
        RequestBody requestFile =
                RequestBody.create(MediaType.parse("multipart/form-data"), file);

        return MultipartBody.Part.createFormData(key, file.getName(), requestFile);
    }

    public List<MultipartBody.Part> getFilesRequestBody(File[] files, Map<String, String> params, String key) {
        List<MultipartBody.Part> parts = new ArrayList<>();

        for (File f : files) {
            RequestBody requestFile =
                    RequestBody.create(MediaType.parse("multipart/form-data"), f);
            MultipartBody.Part formData = MultipartBody.Part.createFormData(key, f.getName(), requestFile);
            parts.add(formData);
        }
        return parts;
    }

    private String toJsonText(Object req) {
        String reqJson = "";
        try {
            Gson gson = new GsonBuilder().disableHtmlEscaping().create();
            reqJson = gson.toJson(req);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return reqJson;
    }

    public void cancel(String url) {
        if (TextUtils.isEmpty(url)) {
            return;
        }
        Subscription subscription = getSubscription().remove(url);
        if (subscription != null) {
            subscription.unsubscribe();
        }
    }

    public ConcurrentHashMap<String, Subscription> getSubscription() {
        return mSubMap;
    }

    private static class Inner {
        private static final RealCall INSTANCE = new RealCall();
    }
}
